package gorpc

import (
	"context"
	"errors"
	"sync"
	"time"
)

// Common errors for RPC calls
var (
	ErrTimeout       = errors.New("rpc: call timeout")
	ErrCallCanceled  = errors.New("rpc: call canceled")
	ErrInvalidState  = errors.New("rpc: invalid call state")
	ErrContextClosed = errors.New("rpc: context closed")
	ErrNetworkDown   = errors.New("rpc: network connection lost")
)

// Response represents an RPC call response
type Response struct {
	Data []byte
	Err  error
}

// RPCCallContext manages a single RPC call's lifecycle
type RPCCallContext struct {
	seqID    int32
	ctx      context.Context
	cancel   context.CancelFunc
	response []byte
	err      error
	done     chan struct{}
	once     sync.Once
}

// NewRPCCallContext creates a new call context with the given sequence ID and timeout
func NewRPCCallContext(seqID int32, timeout time.Duration) *RPCCallContext {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	return &RPCCallContext{
		seqID:  seqID,
		ctx:    ctx,
		cancel: cancel,
		done:   make(chan struct{}),
	}
}

// Complete marks the call as complete with the given response and error
func (c *RPCCallContext) Complete(response []byte, err error) {
	c.once.Do(func() {
		c.response = response
		c.err = err
		c.cancel()
		close(c.done)
	})
}

// Wait blocks until the call is complete or the context is cancelled
func (c *RPCCallContext) Wait() ([]byte, error) {
	select {
	case <-c.ctx.Done():
		if c.err != nil {
			return nil, c.err
		}
		if errors.Is(c.ctx.Err(), context.DeadlineExceeded) {
			return nil, ErrTimeout
		}
		return nil, c.ctx.Err()
	case <-c.done:
		return c.response, c.err
	}
}

// GetSeqID returns the sequence ID of this call
func (c *RPCCallContext) GetSeqID() int32 {
	return c.seqID
}

// Cancel cancels the call context
func (c *RPCCallContext) Cancel() {
	c.cancel()
}

// SequenceID returns the unique sequence ID of the call
func (c *RPCCallContext) SequenceID() int32 {
	return c.seqID
}

// ForceComplete forces completion with a specific error, used for connection loss scenarios
func (c *RPCCallContext) ForceComplete(err error) {
	if err == nil {
		err = ErrNetworkDown
	}
	c.Complete(nil, err)
}

// Done returns a channel that's closed when the call is complete
func (c *RPCCallContext) Done() <-chan struct{} {
	return c.ctx.Done()
}

// Context returns the underlying context
func (c *RPCCallContext) Context() context.Context {
	return c.ctx
}

// Timeout returns the timeout duration
func (c *RPCCallContext) Timeout() time.Duration {
	deadline, ok := c.ctx.Deadline()
	if !ok {
		return 30 * time.Second // Default timeout
	}
	return time.Until(deadline)
}

// RequestID generates a unique request ID using a combination of timestamp and sequence
type RequestID uint64

// NewRequestID generates a new unique request ID
func NewRequestID() RequestID {
	return RequestID(time.Now().UnixNano())
}
